<template>
  <div class="container">
    <div class="aside" ref="asideEl">
      <div class="title">可拖动到右侧画布→</div>
      <div
        class="item"
        :class="{ disabled: state.dropedNodes.some((it) => it.id === item.id) }"
        v-for="(item, index) in state.nodesList"
        :key="index"
        @mousedown="(e) => startCreateNode(e, item)"
      >
        <div class="left">
          <img :src="item.image" alt="" class="image" />
          <div class="info">
            <div class="name">{{ item.label }}</div>
            <div class="sub">{{ item.subLabel }}</div>
          </div>
        </div>
        <div class="icon">
          <el-icon><CaretRight /></el-icon>
        </div>
      </div>
    </div>
    <div class="graph" ref="graphEl"></div>
    <!-- vue节点传送门 -->
    <TeleportContainer />
    <button class="button" @click="exportData">导出数据</button>
  </div>
</template>

<script setup>
import { Graph, Shape } from "@antv/x6";
// import { Stencil } from "@antv/x6-plugin-stencil";
// import { Transform } from "@antv/x6-plugin-transform";
// import { Scroller } from "@antv/x6-plugin-scroller";
import { Selection } from "@antv/x6-plugin-selection";
import { Snapline } from "@antv/x6-plugin-snapline";
import { Keyboard } from "@antv/x6-plugin-keyboard";
import { Clipboard } from "@antv/x6-plugin-clipboard";
import { History } from "@antv/x6-plugin-history";
import { Dnd } from "@antv/x6-plugin-dnd";
import { register, getTeleport } from "@antv/x6-vue-shape";
import { onMounted, provide, reactive, ref, unref, watch } from "vue";
import { useExchangeData } from "../pinia/pinia";
import customVueNodeVue from "./customVueNode.vue";
const TeleportContainer = getTeleport();

const state = reactive({
  // 所有可用节点，可通过异步请求获取
  nodesList: [
    {
      id: 1,
      label: "阀门编号",
      subLabel: "阀门名称",
      image:
        "https://img0.baidu.com/it/u=2160901077,1032449260&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=749",
    },
    {
      id: 2,
      label: "Http",
      subLabel: "阀门名称0",
      image:
        "https://pic3.zhimg.com/50/v2-e55fd75f4c808d59624474904ae93039_hd.jpg",
    },
    {
      id: 3,
      label: "Api",
      subLabel: "阀门名称1",
      image:
        "https://img0.baidu.com/it/u=1437927748,1971909415&fm=253&fmt=auto&app=138&f=JPEG?w=667&h=500",
    },
    {
      id: 4,
      label: "Sql",
      subLabel: "阀门名称2",
      image:
        "https://img2.baidu.com/it/u=699469868,2952981715&fm=253&fmt=auto&app=138&f=JPEG?w=500&h=403",
    },
    {
      id: 5,
      label: "Clound",
      subLabel: "阀门名称3",
      image:
        "https://ss3.baidu.com/-fo3dSag_xI4khGko9WTAnF6hhy/zhidao/pic/item/1f178a82b9014a90b02fcd13a9773912b31bee0f.jpg",
    },
    {
      id: 6,
      label: "Mq",
      subLabel: "阀门名称4",
      image:
        "https://img0.baidu.com/it/u=2977762979,44167134&fm=253&fmt=auto&app=138&f=JPEG?w=889&h=500",
    },
  ],
  dropedNodes: [], //已添加到画布中的节点
});
const changeData = (data,node) => {
  console.log(data, "data");
  // 此处接收节点内部 switch开关时候的数据
  // 可以再这里处理逻辑
  node.replaceData(data)
};
provide("changeData", changeData);

const asideEl = ref(null),
  graphEl = ref(null);
let graph = null,
  dnd = null;
// 节点的连接桩
const ports = {
  groups: {
    top: {
      position: "top",
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: "#5F95FF",
          strokeWidth: 1,
          fill: "#fff",
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    right: {
      position: "right",
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: "#5F95FF",
          strokeWidth: 1,
          fill: "#fff",
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    bottom: {
      position: "bottom",
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: "#5F95FF",
          strokeWidth: 1,
          fill: "#fff",
          style: {
            visibility: "hidden",
          },
        },
      },
    },
    left: {
      position: "left",
      attrs: {
        circle: {
          r: 4,
          magnet: true,
          stroke: "#5F95FF",
          strokeWidth: 1,
          fill: "#fff",
          style: {
            visibility: "hidden",
          },
        },
      },
    },
  },
  items: [
    {
      group: "top",
    },
    {
      group: "right",
    },
    {
      group: "bottom",
    },
    {
      group: "left",
    },
  ],
};
const exportData = () => {
  const jsonData = graph.toJSON();
  console.log(jsonData, "jsonData");
};
// 初始化一个节点，如果没有回显数据可以执行该方法。需要在异步判断之后或者按需调用
const addInitNode = () => {
  // 添加一个初始节点
  graph.addNode({
    data: {
      nodeName: "rootName",
    },
    shape: "circle",
    x: 100,
    y: 40,
    width: 40,
    height: 40,
    ports,
    attrs: {
      circle: {
        fill: "#5F95F0",
        stroke: "#5F95F0",
      },
      text: {
        fill: "#fff",
      },
    },
    label: "总",
  });
};
const init = () => {
  // 初始化实例
  graph = new Graph({
    container: unref(graphEl),
    grid: true,
    panning: false,
    autoResize: true,
    mousewheel: {
      enabled: true,
      zoomAtMousePosition: true,
      modifiers: "ctrl",
      minScale: 0.5,
      maxScale: 3,
    },
    connecting: {
      router: "manhattan",
      allowMulti: false, //是否允许在相同的起始节点和终止之间创建多条边，默认为 true。
      allowLoop: false, //是否允许连接到画布空白位置的点，默认为 true。
      allowBlank: false, //是否允许连接到画布空白位置的点，默认为 true。
      allowEdge: false, //是否允许连接到画布空白位置的点，默认为 true。
      connector: {
        name: "rounded",
        args: {
          radius: 8,
        },
      },
      anchor: "center",
      connectionPoint: "anchor",
      snap: {
        radius: 20,
      }, //当 snap 设置为 true 或者 false 代表开启和关闭连线过程中自动吸附，开启时距离目标 50px 是触发吸附。可以通过配置 radius 属性来自定义吸附半径。
      createEdge() {
        return new Shape.Edge({
          // tools: ["button-remove"],
          attrs: {
            line: {
              stroke: "#A2B1C3",
              strokeWidth: 2,
              targetMarker: {
                name: "block",
                width: 12,
                height: 8,
              },
            },
          },
          zIndex: 0,
        });
      },
      validateConnection({ targetMagnet }) {
        return !!targetMagnet;
      },
    },
    highlighting: {
      magnetAdsorbed: {
        name: "stroke",
        args: {
          attrs: {
            fill: "#5F95FF",
            stroke: "#5F95FF",
          },
        },
      },
    },
  });
  dnd = new Dnd({
    target: graph,
    // 节点放置到画布中的时候
    getDropNode(node) {
      // 存储当前已经放入画布的节点
      state.dropedNodes.push(node.data);
      return node.clone({ keepId: true });
    },
  });
  // 如果有回显的数据，这里可以初始这个节点。可以根据异步情况判定决定是否执行此函数
  addInitNode();
  // #region 使用插件
  graph
    // 图形变换插件
    // .use(
    //   new Transform({
    //     resizing: true,
    //     rotating: true,
    //   })
    // )
    .use(
      new Selection({
        rubberband: true,
        showNodeSelectionBox: true,
      })
    )
    // .use(
    //   new Scroller({
    //     enabled: true,
    //   })
    // )
    .use(new Snapline())
    .use(new Keyboard())
    .use(new Clipboard())
    .use(new History());
  // #endregion
  // 注册自定义节点
  registerNodes();
  // 给画布绑定事件
  bindGraphEvents(graph);
};
// 开始创建拖拽的节点
const startCreateNode = (e, item) => {
  const disabled = state.dropedNodes.some((it) => it.id === item.id);
  if (!disabled) {
    const node = graph.createNode({
      shape: "custom-vue-node",
      data: item,
      // tools: ["button-remove"],
    });
    dnd.start(node, e);
  }
};
// 为画布绑定事件
const bindGraphEvents = (graph) => {
  // #region 快捷键与事件
  graph.bindKey(["meta+c", "ctrl+c"], () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.copy(cells);
    }
    return false;
  });
  graph.bindKey(["meta+x", "ctrl+x"], () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.cut(cells);
    }
    return false;
  });
  graph.bindKey(["meta+v", "ctrl+v"], () => {
    if (!graph.isClipboardEmpty()) {
      const cells = graph.paste({ offset: 32 });
      graph.cleanSelection();
      graph.select(cells);
    }
    return false;
  });

  // undo redo
  graph.bindKey(["meta+z", "ctrl+z"], () => {
    if (graph.canUndo()) {
      graph.undo();
    }
    return false;
  });
  graph.bindKey(["meta+shift+z", "ctrl+shift+z"], () => {
    if (graph.canRedo()) {
      graph.redo();
    }
    return false;
  });

  // select all
  graph.bindKey(["meta+a", "ctrl+a"], () => {
    const nodes = graph.getNodes();
    if (nodes) {
      graph.select(nodes);
    }
  });

  // delete
  graph.bindKey("backspace", () => {
    const cells = graph.getSelectedCells();
    if (cells.length) {
      graph.removeCells(cells);
    }
  });

  // zoom
  graph.bindKey(["ctrl+1", "meta+1"], () => {
    const zoom = graph.zoom();
    if (zoom < 1.5) {
      graph.zoom(0.1);
    }
  });
  graph.bindKey(["ctrl+2", "meta+2"], () => {
    const zoom = graph.zoom();
    if (zoom > 0.5) {
      graph.zoom(-0.1);
    }
  });

  // 控制连接桩显示/隐藏
  const showPorts = (ports, show) => {
    for (let i = 0, len = ports.length; i < len; i += 1) {
      ports[i].style.visibility = show ? "visible" : "hidden";
    }
  };
  graph.on("node:mouseenter", () => {
    const ports = unref(graphEl).querySelectorAll(".x6-port-body");
    showPorts(ports, true);
  });
  graph.on("node:mouseleave", () => {
    const ports = unref(graphEl).querySelectorAll(".x6-port-body");
    showPorts(ports, false);
  });
  // #endregion

  // 移入节点添加删除按钮
  graph.on("node:mouseenter", ({ node }) => {
    // 总 字节点不可删除
    if (node.data?.nodeName !== "rootName") {
      node.addTools({
        name: "button-remove",
        args: {
          onClick({ view }) {
            state.dropedNodes = state.dropedNodes.filter(
              (item) => item.id !== node.data.id
            );
            graph.removeCell(view.cell);
          },
        },
      });
    }
  });
  // 鼠标移开时移除删除按钮
  graph.on("node:mouseleave", ({ node }) => {
    node.removeTools(); // 删除所有的工具
  });

  graph.on("edge:mouseenter", ({ edge }) => {
    edge.addTools({
      name: "button-remove",
    });
  });
  // 鼠标移开时移除删除按钮
  graph.on("edge:mouseleave", ({ edge }) => {
    edge?.removeTools(); // 删除所有的工具
  });

  // 点击空白区域 取消交换
  graph.on("blank:click", () => {
    exchangeData.setData([]);
  });
};
// 注册自定义节点
const registerNodes = () => {
  // 注册vue类型的节点
  register({
    shape: "custom-vue-node",
    width: 50,
    height: 50,
    component: customVueNodeVue,
    ports: { ...ports },
  });
};
onMounted(() => {
  init();
});
// 备用交换的数据
const exchangeData = useExchangeData();
watch(
  () => exchangeData.data,
  (value, old) => {
    if (value.length === 2) {
      const [cell1, cell2] = value;
      const cell1Data = JSON.parse(JSON.stringify(cell1.data))
      const cell2Data = JSON.parse(JSON.stringify(cell2.data))
      console.log({cell2Data,cell1Data})
      cell1.replaceData(cell2Data);
      cell2.replaceData(cell1Data);
      exchangeData.setData([]);
    }
  },
  {
    deep: true,
    immediate: true,
  }
);
</script>

<style lang="scss" scoped>
.container {
  height: 100vh;
  display: flex;
  position: relative;
  .button {
    position: absolute;
    right: 20px;
    top: 20px;
  }
  .aside {
    width: 200px;
    flex-shrink: 0;
    height: 100%;
    box-shadow: 1px 0 1px rgba($color: #000000, $alpha: 0.1);
    position: relative;
    padding: 0 10px;
    box-sizing: border-box;
    .title {
      padding: 10px 0;
    }
    .item {
      display: flex;
      justify-content: flex-start;
      align-items: center;
      margin-bottom: 10px;
      user-select: none;
      cursor: grab;
      .left {
        flex: 1;
        display: flex;
        align-items: center;
        background-color: #5f95f0;
        padding: 0 10px;
        height: 50px;
        $s: 30px;
        .image {
          width: $s;
          height: $s;
          margin-right: 10px;
          object-fit: cover;
        }
        .info {
          .name {
            font-size: 15px;
            font-weight: 600;
            color: #fff;
          }
          .sub {
            font-size: 12px;
            color: #fff;
          }
        }
      }
      &.disabled {
        cursor: not-allowed;
        .icon {
          visibility: hidden;
        }
        .left {
          filter: grayscale(0.9);
        }
      }
    }
  }
  .graph {
    flex: 1;
    height: 100%;
  }
}
</style>
